Make Firewall rules for Passive FTP which uses dynamic port

firewallftpiptableslinuxnetworking

The following two rules allow for passive transfers which i added as Firewall rules for my FTP server.

//The following two rules allow the inbound FTP connection
iptables -A INPUT -s $hostIP -p tcp --dport 21 -i eth0 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -d $hostIP -p tcp --sport 21 -o eth0 -m state --state ESTABLISHED -j ACCEPT

// The following two rules allow for passive transfers
iptables -A INPUT -s $hostIP -p tcp --dport 1024:65535 -i eth0 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -d $hostIP  -p tcp --sport 1024:65535 -o eth0 -m state --state ESTABLISHED -j ACCEPT

My FTP was server configured by assigning passive port to range "1024:65535" and above rules worked. But now FTP server configured to bind any free port instead fix port range. So what changes required in above two rules?

Edit
After applying three rules for passive FTP connection mentioned in answer i have rules in following order and now it's stopped working means client is connected but unable to retrieve remote directory.

//The following two rules allow the inbound FTP connection
iptables -A INPUT -s $hostIP -p tcp --dport 21 -i eth0 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -d $hostIP -p tcp --sport 21 -o eth0 -m state --state ESTABLISHED -j ACCEPT

iptables  -A PREROUTING -t raw -p tcp -s $hostIP --dport 21 -j CT --helper ftp
iptables  -A INPUT -m conntrack --ctstate RELATED -m helper --helper ftp -s $hostIP -p tcp -j ACCEPT
iptables  -A OUTPUT -m conntrack --ctstate ESTABLISHED -m helper --helper ftp -d $hostIP -p tcp -j ACCEPT

Working Rules

iptables -A PREROUTING -t raw -p tcp -s $hostIP --dport 21 -j CT --helper ftp
iptables -A INPUT  -i eth0 -p tcp -s $hostIP -m conntrack --ctstate RELATED,ESTABLISHED -m helper --helper ftp -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp -d $hostIP -m conntrack --ctstate ESTABLISHED -m helper --helper ftp -j ACCEPT

Best Answer

I assume $hostIP in your rules means a host you wish to allow FTP access for, otherwise your existing rules won't make sense to me.

If you are using unencrypted FTP, you should replace your wide-open FTP data connection rules with connection tracking.

First, add a rule that attaches a FTP connection tracking helper to any incoming FTP command connections:

iptables -A PREROUTING -t raw -p tcp -s $hostIP --dport $ftpCMDport -d $ftpServerIP -j CT --helper ftp

Here, $ftpCMDport is the port in which your local FTP server accepts logins; usually it's port 21.

(Historical note: this used to happen automatically for TCP port 21, but it turned out the automatic assignment could be abused, so the automatic assignment of connection tracking helpers was made optional in Linux kernel 3.5, and later the automatic assignment feature was completely removed.)

Once the FTP command connections are being monitored by the CT helper, the firewall will "know" which ports should be allowed for legitimate FTP data connections. You'll need two more rules to actually use this information to allow incoming data connections:

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -m helper \
   --helper ftp -s $hostIP -d $ftpServerIP -p tcp -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -m helper \
   --helper ftp -s $ftpServerIP -d $hostIP -p tcp -j ACCEPT

Together, these three rules should entirely replace your existing two rules for FTP data connections. These should be good for both active and passive connections, should your FTP server allow both types. If you only want to accept passive FTP data connections, you might want to remove the RELATED in the OUTPUT rule.

This was based on: https://home.regit.org/netfilter-en/secure-use-of-helpers/


If you are using SSL/TLS encrypted FTP, then the connection tracking helper won't be able to make sense of the encrypted FTP command traffic, and so if the FTP server will accept data connections in any free port, you cannot effectively firewall traffic by TCP ports at all, since any TCP port could become a FTP data port for some connection. Your only possibility would then be to limit traffic by IP addresses:

iptables -A INPUT -s $hostIP -p tcp -i eth0 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -d $hostIP  -p tcp -o eth0 -m state --state ESTABLISHED -j ACCEPT

Note that these are essentially your existing rules, with the --dport and --sport options removed.

Related Question